﻿// ----------------------------------------------------------------------------------
// Microsoft Developer & Platform Evangelism
// 
// Copyright (c) Microsoft Corporation. All rights reserved.
// 
// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, 
// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES 
// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
// ----------------------------------------------------------------------------------
// The example companies, organizations, products, domain names,
// e-mail addresses, logos, people, places, and events depicted
// herein are fictitious.  No association with any real company,
// organization, product, domain name, email address, logo, person,
// places, or events is intended or should be inferred.
// ----------------------------------------------------------------------------------

namespace Microsoft.Samples.WindowsPhoneCloud.Web.Controllers
{
    using System;
    using System.Collections.Generic;
    using System.Globalization;
    using System.IO;
    using System.Linq;
    using System.Net.Security;
    using System.Net.Sockets;
    using System.Security.Authentication;
    using System.Security.Cryptography.X509Certificates;
    using System.Text;
    using System.Web.Mvc;
    using Microsoft.Samples.WindowsPhoneCloud.Web.Infrastructure;
    using Microsoft.Samples.WindowsPhoneCloud.Web.Models;
    using Microsoft.Samples.WindowsPhoneCloud.Web.UserAccountWrappers;
    using Microsoft.WindowsAzure;
    using Microsoft.WindowsAzure.StorageClient;
    using WindowsPhone.Recipes.Push.Messages;

    [CustomAuthorize(Roles = PrivilegeConstants.AdminPrivilege)]
    public class PushNotificationsController : Controller
    {
        private const int DefaultPort = 2195;

        private readonly CloudQueueClient cloudQueueClient;
        private readonly IPushUserEndpointsRepository pushUserEndpointsRepository;

#if ACS
        private readonly IUserRepository userRepository;

        public PushNotificationsController()
            : this(null, new UserTablesServiceContext(), new UserTablesServiceContext())
        {
        }

        [CLSCompliant(false)]
        public PushNotificationsController(CloudQueueClient cloudQueueClient, IPushUserEndpointsRepository pushUserEndpointsRepository, IUserRepository userRepository)
        {
            if (GetStorageAccountFromConfigurationSetting() == null)
            {
                if (cloudQueueClient == null)
                {
                    throw new ArgumentNullException("cloudQueueClient", "Cloud Queue Client cannot be null if no configuration is loaded.");
                }
            }

            this.cloudQueueClient = cloudQueueClient ?? GetStorageAccountFromConfigurationSetting().CreateCloudQueueClient();
            this.userRepository = userRepository;
            this.pushUserEndpointsRepository = pushUserEndpointsRepository;
        }

        public ActionResult Microsoft()
        {
            var users = this.pushUserEndpointsRepository
                .GetAllPushUsers()
                .Select(userId => new UserModel { UserId = userId, UserName = this.userRepository.GetUser(userId).Name });

            return this.View(users);
        }
#else
        private readonly IMembershipService membershipService;

        public PushNotificationsController()
            : this(null, new UserTablesServiceContext(), new AccountMembershipService())
        {
        }

        [CLSCompliant(false)]
        public PushNotificationsController(CloudQueueClient cloudQueueClient, IPushUserEndpointsRepository pushUserEndpointsRepository, IMembershipService membershipService)
        {
            if (GetStorageAccountFromConfigurationSetting() == null)
            {
                if (cloudQueueClient == null)
                {
                    throw new ArgumentNullException("cloudQueueClient", "Cloud Queue Client cannot be null if no configuration is loaded.");
                }
            }

            this.cloudQueueClient = cloudQueueClient ?? GetStorageAccountFromConfigurationSetting().CreateCloudQueueClient();
            this.membershipService = membershipService;
            this.pushUserEndpointsRepository = pushUserEndpointsRepository;
        }

        public ActionResult Microsoft()
        {
            var users = this.pushUserEndpointsRepository
                .GetAllPushUsers()
                .Select(userId => new UserModel { UserId = userId, UserName = this.membershipService.GetUserByProviderUserKey(new Guid(userId)).UserName });

            return this.View(users);
        }
#endif

        public ActionResult Apple()
        {
            return this.View();
        }

        [HttpPost]
        public ActionResult SendMicrosoftToast(string userId, string message)
        {
            if (string.IsNullOrWhiteSpace(message))
            {
                return this.Json("The notification message cannot be null, empty nor white space.", JsonRequestBehavior.AllowGet);
            }

            var resultList = new List<MessageSendResultLight>();
            var uris = this.pushUserEndpointsRepository.GetPushUsersByName(userId).Select(u => u.ChannelUri);

            var pushUserEndpoint = this.pushUserEndpointsRepository.GetPushUsersByName(userId).FirstOrDefault();

            var toast = new ToastPushNotificationMessage
            {
                SendPriority = MessageSendPriority.High,
                Title = message
            };

            foreach (var uri in uris)
            {
                var messageResult = toast.SendAndHandleErrors(new Uri(uri));
                resultList.Add(messageResult);
                if (messageResult.Status.Equals(MessageSendResultLight.Success))
                {
                    this.QueueMessage(pushUserEndpoint, message);
                }
            }

            return this.Json(resultList, JsonRequestBehavior.AllowGet);
        }

        [HttpPost]
        public ActionResult SendMicrosoftTile(string userId, string message)
        {
            if (string.IsNullOrWhiteSpace(message))
            {
                return this.Json("The notification message cannot be null, empty nor white space.", JsonRequestBehavior.AllowGet);
            }

            var resultList = new List<MessageSendResultLight>();
            var pushUserEndpointList = this.pushUserEndpointsRepository.GetPushUsersByName(userId);
            foreach (var pushUserEndpoint in pushUserEndpointList)
            {
                var tile = new TilePushNotificationMessage
                {
                    SendPriority = MessageSendPriority.High,
                    Count = ++pushUserEndpoint.TileCount
                };

                var messageResult = tile.SendAndHandleErrors(new Uri(pushUserEndpoint.ChannelUri));
                resultList.Add(messageResult);
                if (messageResult.Status.Equals(MessageSendResultLight.Success))
                {
                    this.QueueMessage(pushUserEndpoint, message);

                    this.pushUserEndpointsRepository.UpdatePushUserEndpoint(pushUserEndpoint);
                }
            }

            return this.Json(resultList, JsonRequestBehavior.AllowGet);
        }

        [HttpPost]
        public ActionResult SendMicrosoftRaw(string userId, string message)
        {
            if (string.IsNullOrWhiteSpace(message))
            {
                return this.Json("The notification message cannot be null, empty nor white space.", JsonRequestBehavior.AllowGet);
            }

            var resultList = new List<MessageSendResultLight>();
            var uris = this.pushUserEndpointsRepository.GetPushUsersByName(userId).Select(u => u.ChannelUri);
            var raw = new RawPushNotificationMessage
            {
                SendPriority = MessageSendPriority.High,
                RawData = Encoding.UTF8.GetBytes(message)
            };

            foreach (var uri in uris)
            {
                resultList.Add(raw.SendAndHandleErrors(new Uri(uri)));
            }

            return this.Json(resultList, JsonRequestBehavior.AllowGet);
        }

        [HttpPost]
        public ActionResult SendAppleMessage(string deviceId, string message)
        {
            if (string.IsNullOrWhiteSpace(deviceId))
            {
                return this.Json("The deviceId cannot be null, empty nor white space.", JsonRequestBehavior.AllowGet);
            }

            if (string.IsNullOrWhiteSpace(message))
            {
                return this.Json("The notification message cannot be null, empty nor white space.", JsonRequestBehavior.AllowGet);
            }

            // Get the Apple Notification Service settings.
            var thumprint = ConfigReader.GetConfigValue("AppleNotificationService.Thumbprint");
            var host = ConfigReader.GetConfigValue("AppleNotificationService.Host");
            var port = 0;
            if (!int.TryParse(ConfigReader.GetConfigValue("AppleNotificationService.Port"), NumberStyles.Integer, CultureInfo.InvariantCulture, out port))
            {
                port = DefaultPort;
            }

            X509Certificate2Collection certCollection = null;
            try
            {
                // Load the Apple Notification Service certificate from the store.
                var clientCert = CertificateUtil.GetCertificate(StoreName.My, StoreLocation.LocalMachine, thumprint);
                certCollection = new X509Certificate2Collection(clientCert);
            }
            catch (Exception exception)
            {
                var errorMessage = string.Format(CultureInfo.InvariantCulture, "Error getting the X509 certificate from the store: {0}", exception.Message);
                return this.Json(errorMessage, JsonRequestBehavior.AllowGet);
            }

            TcpClient client = null;
            try
            {
                // Open connection and connect.
                client = new TcpClient(host, port);
                var sslStream = new SslStream(client.GetStream(), false);

                sslStream.AuthenticateAsClient(host, certCollection, SslProtocols.Tls, false);

                byte[] array = null;
                using (var memoryStream = new MemoryStream())
                {
                    var writer = new BinaryWriter(memoryStream);

                    // Construct the message.
                    writer.Write((byte)0);  // Command
                    writer.Write((byte)0);  // First byte of device ID length
                    writer.Write((byte)32); // Device id length

                    // Convert to hex and write to message.
                    byte[] deviceToken = new byte[deviceId.Length / 2];
                    for (int i = 0; i < deviceToken.Length; i++)
                    {
                        deviceToken[i] = byte.Parse(deviceId.Substring(i * 2, 2), NumberStyles.HexNumber);
                    }

                    writer.Write(deviceToken);

                    // Construct payload within JSON message framework.
                    var payload = string.Format(CultureInfo.InvariantCulture, "{{\"aps\":{{\"alert\":\"{0}\",\"badge\":1}}}}", message);

                    // Write payload data.
                    writer.Write((byte)0);                  // First byte of payload length
                    writer.Write((byte)payload.Length);     // Actual payload length
                    byte[] b1 = Encoding.UTF8.GetBytes(payload);
                    writer.Write(b1);
                    writer.Flush();

                    array = memoryStream.ToArray();
                }

                // Send across the wire.
                sslStream.Write(array);
                sslStream.Flush();
            }
            catch (AuthenticationException exception)
            {
                var errorMessage = string.Format(CultureInfo.InvariantCulture, "Error authenticating against APN: {0}", exception.Message);
                return this.Json(errorMessage, JsonRequestBehavior.AllowGet);
            }
            catch (Exception exception)
            {
                var errorMessage = string.Format(CultureInfo.InvariantCulture, "There was an error sending the notification message: {0}", exception.Message);
                return this.Json(errorMessage, JsonRequestBehavior.AllowGet);
            }
            finally
            {
                // Close the client connection.
                client.Close();
            }

            // Success.
            return this.Json("Success", JsonRequestBehavior.AllowGet);
        }

        private static CloudStorageAccount GetStorageAccountFromConfigurationSetting()
        {
            CloudStorageAccount account = null;
            try
            {
                account = CloudStorageAccount.FromConfigurationSetting("DataConnectionString");
            }
            catch (InvalidOperationException)
            {
                account = null;
            }

            return account;
        }

        private static string GetQueueName(string applicationId, string deviceId, string userId)
        {
            var uniqueName = string.Concat(applicationId, deviceId, userId);

            return string.Concat("notification", uniqueName.GetHashCode());
        }

        private void QueueMessage(PushUserEndpoint pushUser, string message)
        {
            var queueName = GetQueueName(pushUser.PartitionKey, pushUser.RowKey, pushUser.UserId);
            var queue = this.cloudQueueClient.GetQueueReference(queueName);

            queue.CreateIfNotExist();
            queue.AddMessage(new CloudQueueMessage(message));
        }
    }
}
